/// <reference path="./continuation-local-storage.d.ts" />
require('reflect-metadata');
//import { events, testEvents } from "../api/news-hub/event"
import * as cls from 'continuation-local-storage';
import { sync } from 'glob';
import * as Sequelize from 'sequelize';
import * as validator from 'validator';
import env from '../config/env';
import * as Redis from 'ioredis';
const { curry } = require('ramda');
import { tokenService } from "./token";
const sequelize = require('sequelize');
const Task = require('data.task');
sequelize.cls = cls.createNamespace('car');

let namespace = sequelize.cls
const { pipe, is, keys, forEach, map } = require('ramda');

export { Sequelize, validator };

export const redis = new Redis(env.redis.url);

export const db = env.db.url ?
    new Sequelize(env.db.url, env.db.options) :
    new Sequelize(env.db.database, env.db.username, env.db.password, env.db.options);

export type foreignKeyType = string;

export const trans = (option?) => operation => async function () {
    return db.transaction(() => {
        return operation.apply(null, arguments);
    })
};

export const transWithTask = (option?) => operation => {
    let t = namespace.get('transaction')
    let hasTrans = !!t

    const opTask = operation.apply(null, arguments)

    if (hasTrans) return opTask

}

/**
 * 加载所有model,设置association
 * @param db
 * @returns Models
 */
const loadAllModels = async (db) => {
    let Models = {};
    const allModelFiles = sync(`${__dirname}/../api/**/*.model.@(ts|js)`, { ignore: ['**/node_modules/**'] });
    allModelFiles.forEach(path => require(path));
    Models = db.models;
    Object
        .keys(Models)
        .forEach(model => {
            if (Models[model].associate && typeof Models[model].associate === 'function') Models[model].associate(Models);
            if (Models[model].attachScopes && typeof Models[model].attachScopes === 'function') Models[model].attachScopes(Models);
        });

    return db;
};

db.addHook('beforeBulkSync', function (options) {
    return loadAllModels(this);
});

export const initDB = async (force?: boolean) => {
    force = force || false;
    await db.sync({
        force,
    }).catch(e => console.log(e.stack));
};

export const create = curry(function <Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, Values) {
    return new Task((rej, res) => table.create(Values).then(res).catch(rej))
})

export const findOne = curry(function <Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.FindOptions) {
    return new Task((rej, res) => table.findOne(options).then(res).catch(rej))
})

export const updateOne = curry(function <Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, where, newValues) {
    return new Task((rej, res) => table.update(newValues, where).then(res).catch(rej))
})

export const deleteOne = curry(function <Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, where) {
    return new Task((rej, res) => table.destroy(where).then(res).catch(rej))
})

export const findAll = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.FindOptions) =>
    new Task((rej, res) => table.findAll(options).then(res).catch(rej))
);

export const bulkCreate = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.BulkCreateOptions, records) =>
    new Task((rej, res) => table.bulkCreate(records, options).then(res).catch(rej))
);

export const update = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.UpdateOptions, values) =>
    new Task((rej, res) => table.update(values, options).then(res).catch(rej))
);

export const upsert = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.UpsertOptions, values) =>
    new Task((rej, res) => table.upsert(values, options).then(res).catch(rej))
);

export const destroy = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.DestroyOptions) =>
    new Task((rej, res) => table.destroy(options).then(res).catch(rej))
);

export const findOrCreate = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.FindOrInitializeOptions<Attribute>) =>
    new Task((rej, res) => table.findOrCreate(options).then(res).catch(rej))
);

export const findById = curry(<Table extends Sequelize.Model<Instance, Attribute>, Instance, Attribute>(table: Table, options: Sequelize.FindOptions, identifier: number | string) =>
    new Task((rej, res) => table.findById(identifier, options).then(res).catch(rej))
);

export const modifyInstanceProperties = curry((instance: Sequelize.Instance<any> | any, props): Sequelize.Instance<any> | any => {
    if (!props || !is(Object, props)) return;
    Object.assign(instance, props);
    if (is(Function, instance.setDataValue)) forEach(key => instance.setDataValue(key, props[key]), keys(props));
    return instance;
});